with Legend.Roles.Specials;
with Legend.Stages.Rooms;
with Legend.Resources;
with Legend.Renders;
with Legend.Settings;
with Legend.StrRes;
with Legend.Items_Dialog;
with Legend.Hero_State_Dialog;
with Legend.Story_Engine;
package body Legend.Stages.Worlds is

   package Context_Menus is

      CMD_Cure       : constant := 1;
      CMD_Jiedu      : constant := CMD_Cure + 1;
      CMD_State      : constant := CMD_Jiedu + 1;
      CMD_Item       : constant := CMD_State + 1;
      CMD_Fire       : constant := CMD_Item + 1;
      CMD_Sys        : constant := CMD_Fire + 1;
      CMD_Go         : constant := CMD_Sys + 1;
      CMD_Sys_Load   : constant := CMD_Sys * 10 + 1;
      CMD_Sys_Save   : constant := CMD_SYs * 10 + 2;
      CMD_Sys_Exit   : constant := CMD_Sys * 10 + 3;
      CMD_Sys_Load_1 : constant := CMD_Sys_Load * 10 + 1;
      CMD_Sys_Load_2 : constant := CMD_Sys_Load * 10 + 2;
      CMD_Sys_Load_3 : constant := CMD_Sys_Load * 10 + 3;
      CMD_Sys_Save_1 : constant := CMD_Sys_Save * 10 + 1;
      CMD_Sys_Save_2 : constant := CMD_Sys_Save * 10 + 2;
      CMD_Sys_Save_3 : constant := CMD_Sys_Save * 10 + 3;

      Places : constant Uint16_Array := (
         Resources.Leader_Home,
         Resources.Beichou_Home,
         Resources.Nanyuan_Home,
         Resources.Heluo_Inn,
         Resources.Youjian_Inn,
         Resources.Yuelai_Inn,
         Resources.Longmen_Inn,
         Resources.Gaosheng_Inn);
   end Context_Menus;

   overriding
   procedure Load_Map (this : in out World_Type)
   is
      procedure Load_Layer (layer : in out Layer_Access; name : String)
      is
         procedure On_Read (FS : not null access Root_Stream_Type'Class)
         is
         begin
            layer := new World_Layer_Type;
            Layer_Type'Read (FS, layer.all);
         end On_Read;
      begin
         IO_Utils.Read_File_Stream (JY_Path & name & ".002", On_Read'Access);
      end Load_Layer;

   begin
      this.Scenes.Clear;
      for rs of All_World.Scenes.all loop
         if rs.Outer_Entries /= ((0, 0), (0, 0)) then
            this.Scenes.Include(rs.Id);
         end if;
      end loop;
      this.Visibles(ml_Story) := False;
      Load_Layer(this.Layers(ml_Earth), "EARTH");
      Load_Layer(this.Layers(ml_Surface), "SURFACE");
      Load_Layer(this.Layers(ml_Building), "BUILDING");
      Load_Layer(this.Layers(ml_BX), "BUILDX");
      Load_Layer(this.Layers(ml_BY), "BUILDY");
      this.Layers(ml_Sprite) := new World_Layer_Type'(others => (others => 0));
      this.Set_Horse_At(
         All_World.Leader.Boat_Head_Position, 
         Boat,
         All_World.Leader.Boat_Face);
   end Load_Map;

   overriding
   procedure Load_Heroes (this : in out World_type)
   is
      subtype Heroes_Type is Roles.Roles_Type(1);
      heroes : Roles.Roles_Access := new Heroes_Type;
      me : Legend.Roles.Role_Pointer := new Roles.Specials.Player_Type;
   begin
      heroes.Append(me);
      me.Setup(this'Unrestricted_Access, All_World.Heroes(0)'Unchecked_Access);
      me.Position(All_World.Leader.Position_At_World);
      me.Horse_Movies(Resources.Leader_Horse_Movies);
      me.Speed(Settings.Player.Walk.Speed);
      me.Horse(All_World.Leader.Horse);
      me.Face(All_World.Leader.Face);
      me.Stand;
      this.Hero := me;
      Roles.Dispose(this.Heroes);
      this.Heroes := heroes;
      me.Focus;
      this.Cloud.Active := False;
      this.Cloud.Movie := (
         Pictures => Resources.Cloud_Images,
         First    => Random_Cloud,
         Total    => 1,
         Index    => 0,
         Interval => 1.0 / 4.0,
         Timer    => 0);
      this.Cloud.Position := this.Hero.Position;
      this.Cloud.Active := True;
      Interactives.Start_Play_Movie(this.Cloud.Movie);
   end Load_Heroes;

   procedure Get_Horse_At (
      this  : World_Type;
      M     : Point_Type;
      Horse : in out Horse_Type
      )
   is
   begin
      if M = All_World.Leader.Boat_Head_Position then
         Horse := Boat;
      end if;
   end Get_Horse_At;

   procedure Set_Horse_At (
      this  : in out World_Type;
      M     : Point_Type;
      Horse : Horse_Type;
      Face  : Face_Type
      )
   is
   begin
      case Horse is
         when Boat =>
            All_World.Leader.Boat_Head_Position := M;
            All_World.Leader.Boat_Face := Face;
            declare
               id : Uint16_t := 3715;
            begin
               for f in Face_Type loop
                  exit when f = Face;
                  id := id + 4;
               end loop;
               this.Set_Building_At(M, id);
            end;
         when others =>
            null;
      end case;
   end Set_Horse_At;

   function Find_Scene_At (
      this  : in out World_Type;
      Door  : Point_Type;
      Scene : out Uint16_t
      ) return Boolean
   is
   begin
      for id of this.Scenes loop
         if (for some e of All_World.Scenes(id).Outer_Entries => Door = e) then
            Scene := id;
            return true;
         end if;
      end loop;
      Scene := No_Scene;
      return False;
   end Find_Scene_At;

   procedure On_Timer (
      this          : in out World_Type;
      Timer         : Hash_Type;
      Count         : Natural;
      Cancel_Bubble : in out Boolean
      )
   is
   begin
      Stage_Type(this).On_Timer(Timer, Count, Cancel_Bubble);
      if this.Cloud.Active then
         if Timer = this.Cloud.Movie.Timer then
            if Random_Natural(Rnd_Generator, 0, 1) = 0 then
               Add(this.Cloud.Position, (1, -1));
               if not this.Is_In_Sight(this.Cloud.Position) then
                  this.Reset_Cloud;
               end if;
            end if;
            this.Canvas.Invalidate;
         end if;
      end if;
   end On_Timer;

   procedure Paint_Sceneries (this : in out World_Type)
   is
      v, c : Point_Type;
   begin
      Stage_Type(this).Paint_Sceneries;
      if this.Cloud.Active then
         this.Map_To_View(this.Cloud.Position, v);
         this.View_To_Canvas(v, c);
         this.Canvas.Draw_Movie(this.Cloud.Movie, c);
      end if;
   end Paint_Sceneries;

   procedure On_Popup_Menu (
      this : in out World_Type;
      Menubar : in out Menus.Menubar_Pointer
      )
   is
      use Menus;
      use Context_Menus;
      mainmenu      : Menubar_Pointer := Create(this'Unrestricted_Access);
      go_menu       : Menubar_Pointer := Create(mainmenu);
      sysmenu       : Menubar_Pointer := Create(mainmenu);
      sys_load_menu : Menubar_Pointer := Create(sysmenu);
      sys_save_menu : Menubar_Pointer := Create(sysmenu);

      mi : Menuitem_Pointer;
      mi_cure     : Menuitem_Pointer := new Menuitem_Type;
      mi_jiedu    : Menuitem_Pointer := new Menuitem_Type;
      mi_state    : Menuitem_Pointer := new Menuitem_Type;
      mi_item     : Menuitem_Pointer := new Menuitem_Type;
      mi_fire     : Menuitem_Pointer := new Menuitem_Type;
      mi_go       : Menuitem_Pointer := new Menuitem_Type;
      mi_sys      : Menuitem_Pointer := new Menuitem_Type;
      mi_sys_load : Menuitem_Pointer := new Menuitem_Type;
      mi_sys_save : Menuitem_Pointer := new Menuitem_Type;
      mi_sys_exit : Menuitem_Pointer := new Menuitem_Type;
      fire_menu   : Menus.Menubar_Pointer;
      h  : Uint16_t;
   begin
      -- strmenu
      for i in 1 .. 3 loop
         mi := new Menus.Menuitem_Type;
         mi.Caption(StrRes.Numbers.One_To_Ten(i));
         mi.Id(CMD_Sys_Load * 10 + i);
         mi.Close_All_Parent_Menus_On_Click;
         sys_load_menu.Add_Item(mi);
         mi := new Menus.Menuitem_Type;
         mi.Caption(StrRes.Numbers.One_To_Ten(i));
         mi.Id(CMD_Sys_Save * 10 + i);
         mi.Close_All_Parent_Menus_On_Click;
         sys_save_menu.Add_Item(mi);
      end loop;
      begin
         mi := new Menus.Menuitem_Type;
         mi.Caption(StrRes.Boat);
         mi.Id(Context_Menus.CMD_Go * 10);
         mi.Close_All_Parent_Menus_On_Click;
         go_menu.Add_Item(mi);
         for i in Context_Menus.Places'Range loop
            mi := new Menus.Menuitem_Type;
            mi.Caption(To_UTF8_Name(All_World.Scenes(Context_Menus.Places(i)).Name));
            mi.Id(Context_Menus.CMD_Go * 10 + i);
            mi.Close_All_Parent_Menus_On_Click;
            go_menu.Add_Item(mi);
         end loop;
         mi_go.Submenu(go_menu);
      end;
      mi_sys_load.Caption(StrRes.World_Menus.Sys_Load);
      mi_sys_load.Submenu(sys_load_menu);
      mi_sys_save.Caption(StrRes.World_Menus.Sys_Save);
      mi_sys_save.Submenu(sys_save_menu);
      mi_sys_exit.Caption (StrRes.World_Menus.Sys_Exit);
      mi_sys_exit.Id(CMD_Sys_Exit);
      mi_sys_exit.Close_All_Parent_Menus_On_Click;
      sysmenu.Add_Item (mi_sys_load);
      sysmenu.Add_Item (mi_sys_save);
      sysmenu.Add_Item (mi_sys_exit);
      ---------------------------------
      mi_cure.Caption (StrRes.World_Menus.Cure);
      mi_cure.Id(CMD_Cure);
      mi_jiedu.Caption(StrRes.World_Menus.Jiedu);
      mi_jiedu.Id(CMD_Jiedu);
      mi_state.Caption(StrRes.World_Menus.State);
      mi_state.Id(CMD_State);
      mi_item.Caption (StrRes.World_Menus.Item);
      mi_item.Id(CMD_Item);
      mi_fire.Caption (StrRes.World_Menus.Fire);
      --         mi_fire.Id(CMD_Fire);
      mi_sys.Caption  (StrRes.World_Menus.Sys);
      mi_sys.Submenu(sysmenu);
      mi_go.Caption (StrRes.World_Menus.Go);
      mainmenu.Add_Item(mi_cure);
      mainmenu.Add_Item(mi_jiedu);
      mainmenu.Add_Item(mi_state);
      mainmenu.Add_Item(mi_item);
      mainmenu.Add_Item(mi_fire);
      mainmenu.Add_Item(mi_go);
      mainmenu.Add_Item(mi_sys);
      fire_menu := Menus.Create(mainmenu);
      for i in All_World.Leader.Team'Range loop
         h := All_World.Leader.Team(i);
         if h /= No_Hero then
            mi := new Menus.Menuitem_Type;
            mi.Caption(To_UTF8_Name(All_World.Heroes(h).Name));
            mi.Id(Context_Menus.CMD_Fire * 10 + i); 
            mi.Close_All_Parent_Menus_On_Click;
            fire_menu.Add_Item(mi);
         end if;
      end loop;
      mi_Fire.Submenu(fire_menu);
      Menubar := mainmenu;
   end On_Popup_Menu;

   procedure On_Command (
      this : in out World_Type;
      Command : Integer
      )
   is
      use Context_Menus;
   begin
      case Command is
         when CMD_Cure =>
            this.On_Stage_Menu_Cure;
         when CMD_Jiedu =>
            this.On_Stage_Menu_Detoxify;
         when CMD_Item =>
            this.On_World_Menu_Item;
         when CMD_State =>
            this.On_Stage_Menu_State;
         when CMD_Sys_Load_1 | CMD_Sys_Load_2 | CMD_Sys_Load_3 =>
            Set_Game(Uint16_t(Command - CMD_Sys_Load * 10));
            this.Close;
         when CMD_Sys_Save_1 | CMD_Sys_Save_2 | CMD_Sys_Save_3 =>
            this.Hero.Emit("storing the world, please wait...");
            Save_States(Uint16_t(Command mod 10));
         when CMD_Sys_Exit =>
            this.Close;
            this.Hero.Clear_Route;
            Set_Game(No_Game);
         when others => 
            case Command / 10 is
               when CMD_Fire => this.On_World_Menu_Fire(Command mod 10);
               when CMD_Go   => this.On_World_Menu_Go(Command mod 10);
               when others   => null;
            end case;
      end case;
   end On_Command;

   procedure On_World_Menu_Item (this : in out World_Type)
   is
      d : Items_Dialog.Items_Dialog_Type;
      grid : Item_Grid_Type;
   begin
      d.Setup;
      d.Run;
      grid := d.Selected;
      if grid.Id = No_Item then
         return;
      end if;
      this.Hero.Use_Item(All_World.Items(grid.Id));
   end On_World_Menu_Item;

   procedure On_World_Menu_Fire (
      this : in out World_Type;
      Team_Index : Integer
      )
   is
      Team : Uint16_Array renames All_World.Leader.Team;
      H  : Uint16_t;
      Script_Index : Uint16_t;
   begin
      if Team_Index in Team'Range then
         H := Team(Team_Index);
         Script_Index := Resources.Get_Fire_Script_By_Hero(H);
         if Script_Index /= No_Script then
            Story_Engine.Invoke_Story(Script_Index);
         end if;
      end if;
   end On_World_Menu_Fire;

   procedure On_World_Menu_Go (this : in out World_Type; Index : Natural)
   is
   begin
      if Index in Context_Menus.Places'Range then
         declare
            Id : Uint16_t := Context_Menus.Places(Index);
            P : Point_Type := All_World.Scenes(Id).Outer_Entries(1);
         begin
            this.Hero.Search_Route(P);
            this.Hero.Start_Walk;
         end;
      elsif Index = 0 then
         this.Hero.Search_Route(All_World.Leader.Boat_Head_Position);
         this.Hero.Start_Walk;
      end if;
   end On_World_Menu_Go;

   procedure On_Passby (this : in out World_Type)
   is
      Hero   : Roles.Role_Pointer := this.Hero;
      Leader : Leader_State_type renames All_World.Leader;
   begin
      -- is leader 
      if Hero.Prototype.Id = 0 then
         Leader.Position_At_World := Hero.Position;
         Leader.Face := Hero.Face;
         Leader.Horse := Hero.Horse;
      end if;
   end On_Passby;

   function Random_Cloud return Uint16_t
   is
   begin
      return Uint16_t(Random_Natural(Rnd_Generator, 0, 3));
   end Random_Cloud;

   procedure Reset_Cloud (this : in out World_Type)
   is
      sv : Region_Type renames this.Sight_View;
      v  : Point_Type := (sv.Top_Left.x, (sv.Top_Left.y + sv.Bottom_Right.y) / 2);
   begin
      this.View_To_Map(v, this.Cloud.Position);
      this.Cloud.Movie.First := Random_Cloud;
   end Reset_Cloud;

end Legend.Stages.Worlds;
